package com.moonshineproject.text
{	
	import com.moonshineproject.parser.ILineParser;
	import com.moonshineproject.text.events.ChangeEvent;
	import com.moonshineproject.text.events.LineEvent;
	import com.moonshineproject.utils.TextUtil;
	
	import flash.events.FocusEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import mx.controls.HScrollBar;
	import mx.controls.scrollClasses.ScrollBar;
	import mx.core.UIComponent;
	import mx.events.ResizeEvent;
	import mx.events.ScrollEvent;
	import mx.managers.IFocusManagerComponent;
	
	
	/*
		Line-based text editor. Text rendering with Flash Text Engine. 
		DataProvider (String) is split up newline & each TextLineRenderer gets one line to render.
		Only what can be seen on screen is rendered & item-renderers are reused.
		
		This class handles scrolling & rendering, MVC style. 
		Different types of rendering can be triggered with various invalidateSomething() calls,
		upon which a flag will be set & when the frame exists rendering will happen (the Flex way).
		
		Managers handle non-rendering actions and affect TextEditorModel, which is the base for rendering.
		See EditManager, SelectionManager & ColorManager.
		
		WORK IN PROGRESS
		
		TODO:
		Reduce coupling between managers by using Events instead of direct hooks.
		Would also allow for plugins like a 'typewriter' plugin that goes PING on Enter & animates the caret,
		by use of preventDefault() & such.
	*/
	[Style(name="backgroundColor",type="uint",format="Color",inherit="no")]
	[Style(name="backgroundAlpha",type="Number",format="Number",inherit="no")]
	[Style(name="selectionColor",type="uint",format="Color",inherit="yes")]
	[Style(name="selectedLineColor",type="uint",format="Color",inherit="no")]
	[Style(name="selectedLineColorAlpha",type="Number",format="Number",inherit="no")]
	public class TextEditor extends UIComponent implements IFocusManagerComponent
	{	
		internal var itemContainer:UIComponent = new UIComponent();
		
		private var verticalScrollBar:ScrollBar;
		private var horizontalScrollBar:HScrollBar;
		// The square connecting dual scrollbars
		private var scrollbarConnector:UIComponent;
		
		protected var lineHeight:int = TextLineRenderer.lineHeight;
		
		protected var selectionManager:SelectionManager;
		protected var editManager:EditManager;
		protected var colorManager:ColorManager;
		protected var undoManager:UndoManager;
		
		protected var model:TextEditorModel;
		
		private var widthUpdateTime:int;
		private var widthUpdateDelay:int = 100;
		private var widthUpdateDelayer:Timer;
		
		// Style
		private var _backgroundColor:uint = 			0xfdfdfd;
		private var _backgroundAlpha:uint = 			1;
		private var lineNumberBackgroundColor:uint = 	0xf9f9f9;
		private var _selectionColor:uint =				0xd1e3f9;
		private var _selectedLineColor:uint =  			0xedfbfb;
		private var _selectedLineColorAlpha:Number =	1;
		
		// Invalidation flags
		private const INVALID_RESIZE:uint =		1 << 0;
		private const INVALID_SCROLL:uint =		1 << 1;
		private const INVALID_FULL:uint =		1 << 2;
		private const INVALID_SELECTION:uint =	1 << 3;
		private const INVALID_LAYOUT:uint =		1 << 4;
		private const INVALID_WIDTH:uint =		1 << 5;
		private var invalidFlags:uint = 0;
		
		// Getters/Setters
		public function get dataProvider():String
		{
			return model.lines.join(lineDelim); 
		}
		public function set dataProvider(v:String):void 
		{
			// Detect line ending
			if (v.indexOf("\r\n")>-1) _lineDelim = "\r\n";
			else if (v.indexOf("\r")>-1) _lineDelim = "\r";
			else _lineDelim = "\n";
			
			// Split lines according to detected line ending
			var lines:Array = v.split(lineDelim);
			var count:int = lines.length;
			
			// Populate lines into model
			model.lines = new Vector.<TextLineModel>(count);
			
			for (var i:int = 0; i < count; i++)
			{
				model.lines[i] = new TextLineModel(lines[i]);
			}
			
			colorManager.reset();
			
			// Clear undo history (readOnly doesn't have it)
			if (undoManager) undoManager.clear();
			
			// Reset selection state
			model.setSelection(0, 0, 0, 0);
			// Reset scroll
			model.scrollPosition = 0;
			if (verticalScrollBar) verticalScrollBar.scrollPosition = 0;
			
			// If we got breakpoints set before we loaded text, re-set them.
			if (_breakpoints)
			{
				breakpoints = _breakpoints;
				_breakpoints = null;
			}
			
			// Set invalidation flags for render
			invalidateFlag(INVALID_RESIZE);
			invalidateFlag(INVALID_FULL);
		}
		
		private var _lineDelim:String = "\n";
		public function get lineDelim():String
		{
			return _lineDelim;
		}
		
		private var _lineNumberWidth:int = 35;
		public function get lineNumberWidth():int
		{
			return _lineNumberWidth;
		}
		public function set lineNumberWidth(v:int):void
		{
			var t:TextLineRenderer;
			
			for each (t in model.itemRenderersFree)
			{
				t.lineNumberWidth = v;
			}
			for each (t in model.itemRenderersInUse)
			{
				t.lineNumberWidth = v;
			}
			
			_lineNumberWidth = v;
			invalidateLines();
			// Changes availible window width, update scrollbar to reflect.
			invalidateFlag(INVALID_RESIZE);
		}
		
		private var _showScrollBars:Boolean = true;
		public function get showScrollBars():Boolean
		{
			return _showScrollBars;
		}
		public function set showScrollBars(v:Boolean):void
		{
			_showScrollBars = v;
			if (verticalScrollBar)
			{
				if (v) verticalScrollBar.alpha = horizontalScrollBar.alpha = 0;
				else verticalScrollBar.alpha = horizontalScrollBar.alpha = 1;
			} 
		}
		
		private var _showLineNumbers:Boolean = true;
		public function get showLineNumbers():Boolean
		{
			return _showLineNumbers;
		}
		public function set showLineNumbers(v:Boolean):void
		{
			_showLineNumbers = v;
			{
				lineNumberWidth = 0;	
			}
			invalidateLines();
		}
		
		private var _hasFocus:Boolean = false;
		protected function get hasFocus():Boolean
		{
			return _hasFocus;
		}
		protected function set hasFocus(v:Boolean):void
		{
			_hasFocus = v;
			invalidateSelection();
		}
		
		public function setParserAndStyles(parser:ILineParser, styles:Object):void
		{
			colorManager.setParser(parser);
			if (styles) 
			{
				if (!styles['selectedLineColor']) 		styles['selectedLineColor'] = _selectedLineColor;
				if (!styles['selectionColor']) 			styles['selectionColor'] = _selectionColor;
				if (!styles['selectedLineColorAlpha'])	styles['selectedLineColorAlpha'] = _selectedLineColorAlpha;
				
				colorManager.styles = styles;
				
				var t:TextLineRenderer;
				for each (t in model.itemRenderersFree)
				{
					t.styles = styles;
				}
				for each (t in model.itemRenderersInUse)
				{
					t.styles = styles;
				}
				
				invalidateLines();
			}
		}
		
		// Only used to set breakpoints later on.
		private var _breakpoints:Array;
		public function get breakpoints():Array
		{
			// Get breakpoints from line models
			var bps:Array = [];
			for (var i:int = 0; i < model.lines.length; i++)
			{
				var line:TextLineModel = model.lines[i];
				if (line.breakPoint) bps.push(i);
			}
			return bps;
		}
		public function set breakpoints(v:Array):void
		{
			_breakpoints = v; // if it exists when set dataProvider is called we re-populate & remove it.
			for (var i:int = 0; i < v.length; i++)
			{
				var lineNumber:int = v[i];
				if (lineNumber >= model.lines.length) return;
				var line:TextLineModel = model.lines[lineNumber];
				line.breakPoint = true;
			}
		}
		
		
		public function TextEditor(readOnly:Boolean=false):void
		{
			model = new TextEditorModel();
			
			widthUpdateDelayer = new Timer(0, 0);
			widthUpdateDelayer.addEventListener(TimerEvent.TIMER_COMPLETE, calculateTextWidth);
			
			selectionManager = new SelectionManager(this, model);
			colorManager = new ColorManager(this, model);
			colorManager.styles['selectedLineColor'] = _selectedLineColor;
			colorManager.styles['selectionColor'] = _selectionColor;
			colorManager.styles['selectedLineColorAlpha'] = _selectedLineColorAlpha;
			
			if (!readOnly)
			{
				editManager = new EditManager(this, model);
				undoManager = new UndoManager(this, model);
			}
			addEventListener(ChangeEvent.TEXT_CHANGE, handleChange, false, 1);
			addEventListener(LineEvent.COLOR_CHANGE, handleColorChange);
			addEventListener(LineEvent.WIDTH_CHANGE, handleWidthChange);
			
			addEventListener(ResizeEvent.RESIZE, handleResize);
		}
		
		override public function styleChanged(styleProp:String):void
		{
			super.styleChanged(styleProp);
			if (getStyle('backgroundColor') != null)
			{ 
				_backgroundColor = getStyle('backgroundColor');
				invalidateFlag(INVALID_RESIZE);
			}
			if (getStyle('backgroundAlpha') != null)
			{
				_backgroundAlpha = getStyle('backgroundAlpha');
				invalidateFlag(INVALID_RESIZE);
			}
			if (getStyle('selectionColor') != null) 
			{
				_selectionColor = getStyle('selectionColor');
				colorManager.styles['selectionColor'] = _selectionColor;
				invalidateFlag(INVALID_SELECTION);
			}
			if (getStyle('selectedLineColor') != null)
			{
				_selectedLineColor = getStyle('selectedLineColor');
				colorManager.styles['selectedLineColor'] = _selectedLineColor;
				invalidateFlag(INVALID_SELECTION);
			}
			if (getStyle('selectedLineColorAlpha') != null)
			{
				_selectedLineColorAlpha = getStyle('selectedLineColorAlpha');
				colorManager.styles['selectedLineColorAlpha'] = _selectedLineColorAlpha;
				invalidateFlag(INVALID_SELECTION);
			}
		}
		
		override protected function focusInHandler(event:FocusEvent):void
		{
			super.focusInHandler(event);
			
			hasFocus = true;
		}
		
		override protected function focusOutHandler(event:FocusEvent):void
		{
			super.focusOutHandler(event);
			
			hasFocus = false;
		}
		
		public function invalidateLines():void
		{
			invalidateFlag(INVALID_FULL);
			invalidateFlag(INVALID_SELECTION);
			//invalidateFlag(INVALID_RESIZE);
		}
		
		public function invalidateSelection():void
		{
			invalidateFlag(INVALID_SELECTION);
		}
		
		public function getSelection():String
		{
			if (model.hasMultilineSelection)
			{
				var selText:String = "";
				
				var startLine:int = model.selectionStartLineIndex;
				var endLine:int = model.selectedLineIndex;
				
				var start:int = model.selectionStartCharIndex;
				var end:int = model.caretIndex;
				
				if (startLine > endLine)
				{
					startLine = endLine;
					endLine = model.selectionStartLineIndex;
					
					start = end;
					end = model.selectionStartCharIndex;
				}
				
				
				selText = model.lines[startLine].text.substr(start);
				for (var i:int = startLine+1; i < endLine; i++)
				{
					selText += lineDelim + model.lines[i].text;
				}
				selText += lineDelim + model.lines[endLine].text.substr(0, end);
				
				return selText;
				
			}
			else
			{
				start = model.selectionStartCharIndex;
				end = model.caretIndex;
				if (model.selectionStartCharIndex > model.caretIndex) 
				{
					start = end;
					end = model.selectionStartCharIndex;
				}
				
				return model.selectedLine.text.substring(start, end);
			}
		}
		
		private function handleChange(event:ChangeEvent):void
		{
			// Any text change requires line invalidation
			invalidateLines();
		}
		
		private function handleColorChange(event:LineEvent):void
		{
			// Line invalidation is required if the changed line is on-screen
			if (event.line >= model.scrollPosition && event.line <= model.scrollPosition + model.renderersNeeded + 1)
			{
				invalidateLines();
			}
		}
		
		private function handleWidthChange(event:LineEvent):void 
		{
			var line:TextLineModel = model.lines[event.line];
			if (line.width > model.width) 
			{
				model.width = line.width;
				invalidateFlag(INVALID_WIDTH);
			} 
			else 
			{
				var timeDiff:int = getTimer()-widthUpdateTime;
				if (timeDiff < widthUpdateDelay) 
				{
					if (!widthUpdateDelayer.running) 
					{
						widthUpdateDelayer.delay = widthUpdateDelay-timeDiff;
						widthUpdateDelayer.reset();
						widthUpdateDelayer.start();
					}
				}
				else 
				{
					calculateTextWidth();
				}
			}
		}
		
		private function calculateTextWidth(event:TimerEvent = null):void 
		{
			var lines:Vector.<TextLineModel> = model.lines;
			var max:Number = 0;
			for (var i:int = 0; i < lines.length; i++) 
			{
				var line:TextLineModel = lines[i];
				if (line.width > max) 
				{
					max = line.width;
				}
			}
			
			if (model.width != max)
			{
				invalidateFlag(INVALID_WIDTH);
			}
			
			model.width = max;
			widthUpdateTime = getTimer();
		}
		
		
		public function selectLine(lineIndex:int):void
		{
			lineIndex = Math.max(0, Math.min(model.lines.length-1, lineIndex));
			model.removeSelection();
			model.selectedLineIndex = lineIndex;
			
			scrollTo(lineIndex);

			invalidateFlag(INVALID_SELECTION);
		}
		
		public function scrollTo(lineIndex:int):void
		{
			if (checkFlag(INVALID_RESIZE))
			{
				updateSize();
			}
			verticalScrollBar.scrollPosition = Math.min(Math.max(lineIndex, verticalScrollBar.minScrollPosition), verticalScrollBar.maxScrollPosition);
			invalidateFlag(INVALID_SCROLL);
		}
		
		private function handleResize(event:ResizeEvent):void
		{
			invalidateFlag(INVALID_RESIZE);
			invalidateFlag(INVALID_SCROLL);
			invalidateFlag(INVALID_FULL);
		}
		
		override protected function createChildren():void
		{
			super.createChildren();
			
			addChild(itemContainer);
			
			verticalScrollBar = new ScrollBar();
			verticalScrollBar.minScrollPosition = 0;
			verticalScrollBar.lineScrollSize = 1;
			verticalScrollBar.addEventListener(ScrollEvent.SCROLL, handleScroll);
			addChild(verticalScrollBar);
		
			horizontalScrollBar = new HScrollBar();
			horizontalScrollBar.minScrollPosition = 0;
			horizontalScrollBar.lineScrollSize = 1;
			horizontalScrollBar.addEventListener(ScrollEvent.SCROLL, handleScroll);
			addChild(horizontalScrollBar);
		
			scrollbarConnector = new UIComponent();
			scrollbarConnector.graphics.beginFill(0x323232, 1);
			scrollbarConnector.graphics.drawRect(0, 0, 15, 15);
			scrollbarConnector.graphics.endFill();
			addChild(scrollbarConnector);
		
			if (!showScrollBars)
			{
				verticalScrollBar.alpha = 0;
				horizontalScrollBar.alpha = 0;
			} 
			
			addEventListener(MouseEvent.MOUSE_WHEEL, handleMouseWheel);
		}
		
		private function handleMouseWheel(event:MouseEvent):void
		{
			scrollTo(verticalScrollBar.scrollPosition - event.delta);
		}
		
		private function handleScroll(event:ScrollEvent):void 
		{
			invalidateFlag(INVALID_SCROLL);
		}
		
		private function getItemRenderers(howMany:int, beginningAtLine:int):Vector.<TextLineRenderer>
		{
			var ret:Vector.<TextLineRenderer> = new Vector.<TextLineRenderer>();
			for (var i:int = 0; i < howMany; i++)
			{
				var rdr:TextLineRenderer;
				if (model.itemRenderersFree.length > 0)
				{
					rdr = model.itemRenderersFree.pop();
				}
				else
				{ 
					rdr = new TextLineRenderer();
					rdr.lineNumberWidth = _lineNumberWidth;
					rdr.styles = colorManager.styles;
					itemContainer.addChild(rdr);
				}
				
				rdr.model = model.lines[beginningAtLine+i];
				rdr.dataIndex = beginningAtLine+i;
				ret.push(rdr);
			}
			
			return ret;
		}
		
		private function freeItemRenderers(startIndex:int, howMany:int):void 
		{
			var toRemove:Vector.<TextLineRenderer> = model.itemRenderersInUse.splice(startIndex, howMany);
			for each (var rdr:TextLineRenderer in toRemove) 
			{
				rdr.x = -2000;
				rdr.y = -2000;
			}
			
			model.itemRenderersFree = model.itemRenderersFree.concat(toRemove);
		}
		
		private function freeRenderersAtTop(howMany:int):void 
		{
			freeItemRenderers(0, howMany);
		}
		
		private function freeRenderersAtBottom(howMany:int):void
		{
			freeItemRenderers(model.itemRenderersInUse.length-howMany, howMany);
		}
		
		private function clearAllRenderers():void 
		{
			freeItemRenderers(0, model.itemRenderersInUse.length);
		}
		
		
		private function updateDataProvider():void
		{
			clearAllRenderers();
			var needed:int = Math.min(model.renderersNeeded, model.lines.length - model.scrollPosition);
			model.itemRenderersInUse = getItemRenderers(needed, model.scrollPosition);
			
			invalidateFlag(INVALID_LAYOUT);
		}
		
		private function updateSize():void
		{
			model.renderersNeeded = Math.ceil(height/lineHeight);
			
			updateVerticalScrollbar();
			updateHorizontalScrollbar();
			updateScrollbarVisibility();
			
			itemContainer.graphics.clear();
			itemContainer.graphics.beginFill(_backgroundColor, _backgroundAlpha);
			itemContainer.graphics.drawRect(0, 0, width, height);
			itemContainer.graphics.endFill();
			
			if (showLineNumbers)
			{
				// Calculate line-number gutter width according to line count
				lineNumberWidth = 7.85*TextUtil.digitCount(model.lines.length)+8+10;
			
				itemContainer.graphics.beginFill(lineNumberBackgroundColor);
				itemContainer.graphics.drawRect(0, 0, _lineNumberWidth, height);
				itemContainer.graphics.endFill();
			}
		}
		
		private function updateVerticalScrollbar():void
		{
			var maxScroll:int = Math.max(model.lines.length - model.renderersNeeded + 1, 0);
			verticalScrollBar.maxScrollPosition = maxScroll;
			verticalScrollBar.pageSize = model.renderersNeeded;
			verticalScrollBar.visible = maxScroll > 0;
		}
		
		private function updateHorizontalScrollbar():void
		{
			var availWidth:int = width - lineNumberWidth;
			if (verticalScrollBar.visible) availWidth -= 15;
			
			var maxScroll:int = Math.max(model.width - availWidth, 0);
			horizontalScrollBar.maxScrollPosition = maxScroll;
			horizontalScrollBar.pageSize = availWidth;
			
			var old:Boolean = horizontalScrollBar.visible;
			horizontalScrollBar.visible = maxScroll > 0;
			
			if (old != horizontalScrollBar.visible) updateScrollbarVisibility();
			
		}
		
		private function updateScrollbarVisibility():void
		{
			verticalScrollBar.x = width; // Scrollbar draws in negative local space
			verticalScrollBar.height = height;
			
			horizontalScrollBar.y = height;
			horizontalScrollBar.width = width;
			
			if (horizontalScrollBar.visible && verticalScrollBar.visible)
			{
				verticalScrollBar.height -= 15;
				horizontalScrollBar.width -= 15;
				scrollbarConnector.x = width-15;
				scrollbarConnector.y = height-15;
				scrollbarConnector.visible = true;
			}
			else
			{
				scrollbarConnector.visible = false;
			}
		}
		
		private function updateHorizontalScroll():void
		{
			model.horizontalScrollPosition = -horizontalScrollBar.scrollPosition;
			invalidateFlag(INVALID_LAYOUT);
			invalidateFlag(INVALID_SELECTION);
		}
		
		private function updateVerticalScroll():void
		{
			var scrollDelta:int = verticalScrollBar.scrollPosition - model.scrollPosition;
			
			if (scrollDelta == 0) return;
			
			if (Math.abs(scrollDelta) >= model.renderersNeeded) 
			{
				invalidateFlag(INVALID_FULL);
				model.scrollPosition = verticalScrollBar.scrollPosition;
				return;
			}
			
			var bottomLine:int;
			var linesRemaining:int;
			var affectedLines:int;
			var newRenderers:Vector.<TextLineRenderer>;
			
			if (scrollDelta > 0) // Scroll down
			{
				bottomLine = model.scrollPosition + model.renderersNeeded;
				linesRemaining = model.lines.length - bottomLine;
				affectedLines = Math.min(scrollDelta, linesRemaining);
				
				freeRenderersAtTop(scrollDelta);
				newRenderers = getItemRenderers(affectedLines, bottomLine);
				model.itemRenderersInUse = model.itemRenderersInUse.concat(newRenderers);
			}
			else // Scroll up
			{
				linesRemaining = model.scrollPosition;
				affectedLines = Math.min(-scrollDelta, linesRemaining);
				
				freeRenderersAtBottom(affectedLines);
				newRenderers = getItemRenderers(affectedLines, model.scrollPosition - affectedLines);
				model.itemRenderersInUse = newRenderers.concat(model.itemRenderersInUse);
				
				// Restore any unused lines to the bottom
				bottomLine = model.scrollPosition - affectedLines + model.itemRenderersInUse.length;
				linesRemaining = model.lines.length - bottomLine;
				affectedLines = Math.min(model.itemRenderersFree.length, linesRemaining);
				if (affectedLines > 0)
				{
					newRenderers = getItemRenderers(affectedLines, bottomLine);
					model.itemRenderersInUse = model.itemRenderersInUse.concat(newRenderers);
				}
			}
			
			model.scrollPosition = verticalScrollBar.scrollPosition;
			
			invalidateFlag(INVALID_LAYOUT);
			invalidateFlag(INVALID_SELECTION);
		}
		
		private function updateLayout():void
		{
			var yStart:int = 0;
			var rdr:TextLineRenderer;
			for (var i:int = 0; i < model.itemRenderersInUse.length; i++)
			{
				rdr = model.itemRenderersInUse[i];
				rdr.y = yStart;
				rdr.x = 0;
				rdr.horizontalOffset = model.horizontalScrollPosition;
				yStart += lineHeight;
			}
		}
		
		private function updateSelection():void
		{
			var rdr:TextLineRenderer;

			for (var i:int = 0; i < model.itemRenderersInUse.length; i++)
			{
				rdr = model.itemRenderersInUse[i];
				if (i+model.scrollPosition == model.selectedLineIndex)
				{
					rdr.focus = hasFocus;
					rdr.caretPosition = model.caretIndex;
					rdr.drawSelection(model.selectionStartCharIndex, model.caretIndex);
				}
				else
				{
					rdr.focus = false;
					rdr.removeSelection();
				}
			}
		}
		
		private function updateMultilineSelection():void
		{
			var rdr:TextLineRenderer;
			
			// If we are horiz-scrolling the selection might be wider than window width.
			// Makes sure selection is drawn all the way to the right edge when scrolling.
			var lineWidth:int = width - model.horizontalScrollPosition;
			
			for (var i:int = 0; i < model.itemRenderersInUse.length; i++)
			{
				rdr = model.itemRenderersInUse[i];
				if (i+model.scrollPosition == model.selectionStartLineIndex) 
				{ // Beginning of selection (may be below or above current point)
					if (model.selectionStartLineIndex > model.selectedLineIndex)
					{
						rdr.drawSelection(0, model.selectionStartCharIndex);
					}
					else
					{
						rdr.drawFullLineSelection(lineWidth, model.selectionStartCharIndex);
					}
					rdr.focus = false;
				} 
				else if (i+model.scrollPosition == model.selectedLineIndex)
				{ // Selected line
					if (model.selectedLineIndex > model.selectionStartLineIndex)
					{
						rdr.drawSelection(0, model.caretIndex);
					}
					else
					{
						rdr.drawFullLineSelection(lineWidth, model.caretIndex); 
					}
					rdr.caretPosition = model.caretIndex;
					rdr.focus = hasFocus;
				}
				else if (model.selectionStartLineIndex < i+model.scrollPosition 
						 && model.selectedLineIndex > i+model.scrollPosition)
				{ // Start of selection is above current line
					rdr.drawFullLineSelection(lineWidth);
					rdr.focus = false;				  
				}
				else if (model.selectionStartLineIndex > i+model.scrollPosition
						&& model.selectedLineIndex < i+model.scrollPosition)
				{ // Start of selection is below current line
					rdr.drawFullLineSelection(lineWidth);
					rdr.focus = false;
				}
				else
				{ // No selection
					rdr.focus = false;
					rdr.removeSelection();
				}
			}
		}
		
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
		{
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			
			if (checkFlag(INVALID_RESIZE))
			{
				updateSize();
			}
			if (checkFlag(INVALID_WIDTH))
			{
				updateHorizontalScrollbar();
			}
			if (checkFlag(INVALID_SCROLL))
			{
				updateVerticalScroll();
				updateHorizontalScroll();
			}
			if (checkFlag(INVALID_FULL))
			{
				updateDataProvider();
				updateVerticalScrollbar();
				updateHorizontalScrollbar();
			}
			if (checkFlag(INVALID_SELECTION))
			{
				if (model.hasMultilineSelection)
				{
					updateMultilineSelection();
				}
				else
				{
					updateSelection();
				}
			}
			if (checkFlag(INVALID_LAYOUT))
			{
				updateLayout();
			}
			
			// Reset all invalidation flags
			invalidFlags = 0;
		}
		
		private function invalidateFlag(flag:uint):void
		{
			if (invalidFlags == 0)
			{
				// Invalidate display list on the first flag invalidated, to get updateDisplayList to execute
				invalidateDisplayList();
			}
			invalidFlags |= flag;
		}
		
		private function checkFlag(flag:uint):Boolean
		{
			return (invalidFlags & flag) > 0;
		}
		
	}
}